#
# LuckyBot4, a python IRC bot
# (c) Copyright 2008 by Lucas van Dijk
# http://www.return1.net
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3, or (at your option)
# any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA
#
# $Id: plugins.py 61 2009-03-30 23:25:17Z luckyluke56 $
#

import sys
import os
import re
import luckybot.path as path
import imp
import inspect
from luckybot.bot.language import Language
from luckybot.luckynet.protocols.irc import Format
from ConfigParser import SafeConfigParser, NoOptionError

REGEXP_RAW = 1
REGEXP_MESSAGE = 2

class PluginException(Exception):
	pass

class PluginLanguage(Language):
	"""
		Class for plugin language
	"""

	def __init__(self, plugin):
		self.bot = plugin.bot

		if self.bot.settings.has_option('Bot', 'language'):
			super(PluginLanguage, self).__init__(self.bot.settings.get('Bot', 'language'))
		else:
			super(PluginLanguage, self).__init__('english')

	def get(self, key, vars = {}):
		c = self.bot.settings.get('Bot', 'default_color') if self.bot.settings.has_option('Bot', 'default_color') else 'darkblue'
		default_vars = {
			'c': Format.color(c),
			'b': Format.bold(),
			'n': Format.normal(),
			'u': Format.underline(),
			'r': Format.reverse(),

			'prefix': self.bot.settings.get('Bot', 'command_prefix')
		}

		return super(PluginLanguage, self).get(key, dict(vars, **default_vars))

class Plugin(object):
	"""
		This class represents a (loaded) plugin
	"""

	def __init__(self, manager, plugins_dir, dirname):
		self.plugins_dir = plugins_dir
		self.dirname = dirname
		self.manager = manager
		self.bot = manager.bot
		self.lang = PluginLanguage(self)

	def load(self):
		"""
			(Re)Loads the module instance of this plugin
		"""

		if hasattr(self, 'commands'):
			del self.commands

		if hasattr(self, 'regexps'):
			del self.regexps

		if hasattr(self, 'message_handler'):
			del self.message_handler

		self.commands = {}
		self.regexps = []
		self.message_handler = None

		# Check if plugin has metadata
		if not hasattr(self, 'PLUGIN_INFO'):
			raise PluginException, "Plugin %s has no metadata defined" % self.dirname

		self.PLUGIN_INFO['dirname'] = self.dirname

		self._load_language()

		if hasattr(self, 'initialize'):
			self.initialize()

	def _load_language(self):
		"""
			Checks if the plugin has a language file, and reads it,
			if there's one
		"""

		if os.path.exists(os.path.join(self.plugins_dir, self.dirname, 'language.ini')):
			self.lang.read_language(os.path.join(self.plugins_dir, self.dirname, 'language.ini'))

	def unload(self):
		"""
			Unloads the plugin
		"""

		if hasattr(self, 'destroy'):
			self.destroy()

		del self.commands
		del self.regexps
		del self.message_handler

	def register_command(self, name, callback, **keywords):
		"""
			Register a new bot command

			@type name: string
			@param name: The name of the command

			@type callback: function
			@param callback: The function to call when the command is used

			@type keywords: dictionary
			@param keywords: List of extra data you want to give with. Special keywords are:
				help: A short string explaining the command
				args: List of arguments
		"""

		if name in self.commands:
			return

		self.commands[name] = (callback, keywords)
		self.manager.register_command(name, self.dirname)

	def register_regexp(self, regexp, type, callback, userdata = None):
		"""
			Register a new regular expression to match against the incoming data or user message

			@type regexp: string
			@param regexp: The regular expression I{object} which will be used for matching

			@type type: int
			@param type: Where to match against: The Data (REGEXP_RAW) our the user message (REGEXP_MESSAGE)

			@type callback: function
			@param callback: The function to call when the regexp matches

			@param userdata: Any object you want to give with the callback
		"""

		self.regexps.append((regexp, type, callback, userdata))
		self.manager.register_regexp(self.dirname)

	def register_message_handler(self, callback):
		"""
			Registers a 'message handler', the given function will be called on EACH server and/or channel
			message, allowing the plugin to check for JOIN/PART/QUIT and other events.

			@type callback: function
			@param callback: The function to call when we receive a server/channel message
		"""

		self.message_handler = callback
		self.manager.register_message_handler(self.dirname)

	def call(self, name, event):
		"""
			Call the the command callback

			@type name: string
			@param name: The command name

			@type event: luckybot.bot.irc.Event
			@param event: The IRC Event

			@rtype: bool
			@return: True when the command exists, else false
		"""

		if name in self.commands:
			callback, keywords = self.commands[name]

			callback(event)
			return True
		else:
			return False


class Manager(object):
	"""
		This class handles everything related to plugins.
		Registering of bot commands, regexp hooking,
		calling and loading plugins.
	"""

	def __init__(self, bot):
		self.bot = bot
		self.commands = {}
		self.regexps = []
		self.message_handlers = []
		self.plugins = {}
		self.dirs = []
		self.available_plugins = []

	def register_command(self, name, plugin_name):
		"""
			Register command with the plugin manager

			@type name: string
			@param name: The command name

			@type plugin_name: string
			@param plugin_name: The plugin directory name
		"""

		if not name in self.commands:
			self.commands[name] = []

		try:
			self.commands[name].index(plugin_name)
		except Exception, e:
			self.commands[name].append(plugin_name)

	def register_regexp(self, plugin_name):
		"""
			Register a new regular expression to match against the incoming data or user message

			@type plugin_name: string
			@param plugin_name: The directory name of the plugin
		"""

		try:
			self.regexps.index(plugin_name)
		except:
			self.regexps.append(plugin_name)

	def register_message_handler(self, plugin_name):
		"""
			Registers a 'message handler', the given function will be called on EACH server and/or channel
			message, allowing the plugin to check for JOIN/PART/QUIT and other events.

			@type plugin_name: string
			@param plugin_name: The directory name of the plugin
		"""

		try:
			self.message_handler.index(plugin_name)
		except:
			self.message_handlers.append(plugin_name)

	def check_data(self, event):
		"""
			Checks incoming data for matching regexps or commands

			@type event: luckybot.bot.irc.Event
			@param event: An event object containing all data about
				the channel, user and other message details.
		"""

		if self.regexps:
			for plugin in self.regexps:
				if plugin in self.plugins:
					for tuple in self.plugins[plugin].regexps:
						regexp, type, callback, userdata = tuple

						if type == REGEXP_RAW:
							match = regexp.match(event.message.raw)
						else:
							match = regexp.match(event.message.message)

						if match:
							callback(event, match, userdata)

		if self.message_handlers:
			for plugin_name in self.message_handlers:
				if self.plugins.has_key(plugin_name):
					self.plugins[plugin_name].message_handler(event)

		if hasattr(event.message, 'bot_command'):
			if event.message.bot_command in self.commands:
				for plugin_name in self.commands[event.message.bot_command]:
					self.plugins[plugin_name].call(event.message.bot_command, event)

	def load_plugins(self, dir):
		"""
			Loads plugins in a specified directory

			@type dir: string
			@param dir: The directory to read
		"""

		if not os.path.isdir(dir):
			return

		# Get enabled plugins
		plugins = self.bot.settings.items('Disabled')

		disabled = []
		for tuple in plugins:
			plugin, disable = tuple
			if disable:
				disabled.append(plugin)

		for file in os.listdir(dir):
			if file[0] == '_' or file[0] == '.' or not os.path.isdir(os.path.join(dir, file)):
				continue

			self.available_plugins.append((dir, file))

			if file in disabled:
				continue

			self.load_plugin(dir, file)

		self.dirs.append(dir)

	def get_plugin_class(self, dir, name):
		"""
			Get the plugin class object for a specific plugin
		"""

		path = os.path.join(dir, name)

		if not os.path.isdir(path) or not os.path.exists(os.path.join(path, '__init__.py')):
			raise PluginException, "Plugin does not exists"

		sys.path.insert(0, path)
		module_obj = imp.load_source(name, os.path.join(path,'__init__.py'))
		sys.path = sys.path[1:]

		plugin_cls = None
		for cls in module_obj.__dict__.values():
			if hasattr(cls, '__module__'):
				if inspect.isclass(cls) and cls.__module__ == name:
					if issubclass(cls, Plugin):
						plugin_cls = cls
						break

		if plugin_cls == None:
			raise PluginException, "No plugin class defined"

		plugin = plugin_cls(self, dir, name)

		return plugin

	def load_plugin(self, dir, name):
		"""
			loads a given plugin in a specified Direcotory

			@type dir: string
			@param dir: The directory where the plugin dir lies in

			@type name: string
			@param name: The name of the plugin directory

			@rtype: bool
			@return: True on success else false
		"""

		try:
			plugin = self.get_plugin_class(dir, name)

			plugin.load()
		except Exception, e:
			import traceback
			traceback.print_exc()
			self.bot.view.error('Could not load plugin %s' % name)
			return False

		self.plugins[name] = plugin
		return True

	def unload_plugins(self):
		"""
			Unloads all loaded plugins
		"""

		for name, plugin in self.plugins.iteritems():
			self.unload_plugin(name)

	def unload_plugin(self, name):
		"""
			Unloads the specified plugin

			@type name: string
			@param name: The plugin name (directory name)

			@rtype: bool
			@return: True on success, else false
		"""

		# try to find the plugin
		# based on dirname or the plugin name
		if not name in self.plugins:
			got_plugin = False
			for plugin in self.plugins:
				if name == self.plugins[plugin].PLUGIN_INFO['name']:
					name = self.plugins[plugin].PLUGIN_INFO['dirname']
					got_plugin = True
					break

			if got_plugin == False:
				name = False

		if name == False:
			return False

		# Remove registered commands/regexps/handlers
		try:
			self.regexps.remove(name)
		except:
			pass

		try:
			self.message_handlers.remove(name)
		except:
			pass

		for command, plugin_list in self.commands.iteritems():
			try:
				plugin_list.remove(name)
			except:
				pass

		self.plugins[name].unload()
		del self.plugins[name]

		return True

	def reload_plugins(self):
		"""
			Reloads all loaded plugins

			@rtype: bool
			@return: True on success else false
		"""

		success = True

		if self.plugins:
			for name, plugin in self.plugins.iteritems():
				if not self.reload_plugin(name) and succes == True:
					success = False

		return success

	def reload_plugin(self, name):
		"""
			Reloads a given plugin

			@rtype: bool
			@return: True on success else false
		"""

		succes = False

		if name in self.plugins:
			plugins_dir = self.plugins[name].plugins_dir
			if self.unload_plugin(name) and self.load_plugin(plugins_dir, name):
				succes = True

		return succes

	def __del__(self):
		self.unload_plugins()

